篇首语:本文由编程笔记#小编为大家整理,主要介绍了自然语言处理(NLP)基于序列到序列的中-英机器翻译相关的知识,希望对你有一定的参考价值。
作者简介:在校大学生一枚,华为云享专家,阿里云专家博主,腾云先锋(TDP)成员,云曦智划项目总负责人,全国高等学校计算机教学与产业实践资源建设专家委员会(TIPCC)志愿者,以及编程爱好者,期待和大家一起学习,一起进步~
.
博客主页:ぃ灵彧が的学习日志
.
本文专栏:人工智能
.
专栏寄语:若你决定灿烂,山无遮,海无拦
.
本示例教程介绍如何使用飞桨完成一个机器翻译任务。
我们将会使用飞桨提供的LSTM的API,组建一个sequence to sequence with attention的机器翻译的模型,并在示例的数据集上完成从英文翻译成中文的机器翻译。
本示例基于飞桨开源框架2.0版本。
import paddle
import paddle.nn.functional as F
import re
import numpy as np
print(paddle.__version__)
# cpu/gpu环境选择,在 paddle.set_device() 输入对应运行设备。
# device = paddle.set_device('gpu')
输出结果如下图1所示:
我们将使用 http://www.manythings.org/anki/ 提供的中英文的英汉句对作为数据集,来完成本任务。该数据集含有23610个中英文双语的句对。
!wget -c https://www.manythings.org/anki/cmn-eng.zip && unzip cmn-eng.zip
!wc -l cmn.txt
输出结果如下图2所示:
接下来我们通过处理下载下来的双语句对的文本文件,将双语句对读入到python的数据结构中。这里做了如下的处理。
MAX_LEN = 10
lines = open('cmn.txt', encoding='utf-8').read().strip().split('\\n')
words_re = re.compile(r'\\w+')
pairs = []
for l in lines:
en_sent, cn_sent, _ = l.split('\\t')
pairs.append((words_re.findall(en_sent.lower()), list(cn_sent)))
# create a smaller dataset to make the demo process faster
filtered_pairs = []
for x in pairs:
if len(x[0]) < MAX_LEN and len(x[1]) < MAX_LEN and \\
x[0][0] in (&#39;i&#39;, &#39;you&#39;, &#39;he&#39;, &#39;she&#39;, &#39;we&#39;, &#39;they&#39;):
filtered_pairs.append(x)
print(len(filtered_pairs))
for x in filtered_pairs[:10]: print(x)
输出结果如下图3所示&#xff1a;
接下来我们分别创建中英文的词表&#xff0c;这两份词表会用来将英文和中文的句子转换为词的ID构成的序列。词表中还加入了如下三个特殊的词&#xff1a; - : 用来对较短的句子进行填充。 - : “begin of sentence”&#xff0c; 表示句子的开始的特殊词。 - : “end of sentence”&#xff0c; 表示句子的结束的特殊词。
Note: 在实际的任务中&#xff0c;可能还需要通过&#xff08;或者&#xff09;特殊词来表示未在词表中出现的词。
en_vocab &#61;
cn_vocab &#61;
# create special token for pad, begin of sentence, end of sentence
en_vocab[&#39;
cn_vocab[&#39;
en_idx, cn_idx &#61; 3, 3
for en, cn in filtered_pairs:
for w in en:
if w not in en_vocab:
en_vocab[w] &#61; en_idx
en_idx &#43;&#61; 1
for w in cn:
if w not in cn_vocab:
cn_vocab[w] &#61; cn_idx
cn_idx &#43;&#61; 1
print(len(list(en_vocab)))
print(len(list(cn_vocab)))
输出结果如下图4所示&#xff1a;
接下来根据词表&#xff0c;我们将会创建一份实际的用于训练的用numpy array组织起来的数据集。 - 所有的句子都通过补充成为了长度相同的句子。 - 对于英文句子&#xff08;源语言&#xff09;&#xff0c;我们将其反转了过来&#xff0c;这会带来更好的翻译的效果。 - 所创建的padded_cn_label_sents是训练过程中的预测的目标&#xff0c;即&#xff0c;每个中文的当前词去预测下一个词是什么词。
padded_en_sents &#61; []
padded_cn_sents &#61; []
padded_cn_label_sents &#61; []
for en, cn in filtered_pairs:
# reverse source sentence
padded_en_sent &#61; en &#43; [&#39;
padded_en_sent.reverse()
padded_cn_sent &#61; [&#39;
padded_cn_label_sent &#61; cn &#43; [&#39;
padded_en_sents.append([en_vocab[w] for w in padded_en_sent])
padded_cn_sents.append([cn_vocab[w] for w in padded_cn_sent])
padded_cn_label_sents.append([cn_vocab[w] for w in padded_cn_label_sent])
train_en_sents &#61; np.array(padded_en_sents)
train_cn_sents &#61; np.array(padded_cn_sents)
train_cn_label_sents &#61; np.array(padded_cn_label_sents)
print(train_en_sents.shape)
print(train_cn_sents.shape)
print(train_cn_label_sents.shape)
输出结果如下图5所示&#xff1a;
我们将会创建一个Encoder-AttentionDecoder架构的模型结构用来完成机器翻译任务。 首先我们将设置一些必要的网络结构中用到的参数。
embedding_size &#61; 128
hidden_size &#61; 256
num_encoder_lstm_layers &#61; 1
en_vocab_size &#61; len(list(en_vocab))
cn_vocab_size &#61; len(list(cn_vocab))
epochs &#61; 20
batch_size &#61; 16
在编码器的部分&#xff0c;我们通过查找完Embedding之后接一个LSTM的方式构建一个对源语言编码的网络。飞桨的RNN系列的API&#xff0c;除了LSTM之外&#xff0c;还提供了SimleRNN, GRU供使用&#xff0c;同时&#xff0c;还可以使用反向RNN&#xff0c;双向RNN&#xff0c;多层RNN等形式。也可以通过dropout参数设置是否对多层RNN的中间层进行dropout处理&#xff0c;来防止过拟合。
除了使用序列到序列的RNN操作之外&#xff0c;也可以通过SimpleRNN, GRUCell, LSTMCell等API更灵活的创建单步的RNN计算&#xff0c;甚至通过继承RNNCellBase来实现自己的RNN计算单元。
# encoder: simply learn representation of source sentence
class Encoder(paddle.nn.Layer):
def __init__(self):
super(Encoder, self).__init__()
self.emb &#61; paddle.nn.Embedding(en_vocab_size, embedding_size,)
self.lstm &#61; paddle.nn.LSTM(input_size&#61;embedding_size,
hidden_size&#61;hidden_size,
num_layers&#61;num_encoder_lstm_layers)
def forward(self, x):
x &#61; self.emb(x)
x, (_, _) &#61; self.lstm(x)
return x
在解码器部分&#xff0c;我们通过一个去除注意力机制的LSTM来完成解码&#xff0c;也就是一个最简单的encoder-decoder模型架构。
单步的LSTM&#xff1a;在解码器的实现的部分&#xff0c;我们同样使用LSTM&#xff0c;与Encoder部分不同的是&#xff0c;下面的代码&#xff0c;每次只让LSTM往前计算一次。整体的recurrent部分&#xff0c;是在训练循环内完成的。
对于第一次接触这样的网络结构来说&#xff0c;下面的代码在理解起来可能稍微有些复杂&#xff0c;你可以通过插入打印每个tensor在不同步骤时的形状的方式来更好的理解。
# only move one step of LSTM,
# the recurrent loop is implemented inside training loop
class Decoder(paddle.nn.Layer):
def __init__(self):
super(Decoder, self).__init__()
self.emb &#61; paddle.nn.Embedding(cn_vocab_size, embedding_size)
self.lstm &#61; paddle.nn.LSTM(input_size&#61;embedding_size &#43; hidden_size,
hidden_size&#61;hidden_size)
# for computing output logits
self.outlinear &#61;paddle.nn.Linear(hidden_size, cn_vocab_size)
def forward(self, x, previous_hidden, previous_cell, encoder_outputs):
x &#61; self.emb(x)
#encoder_outputs 16*11*256
context_vector &#61; paddle.sum(encoder_outputs, 1)
#context_vector 16*1*256
context_vector &#61; paddle.unsqueeze(context_vector, 1)
#lstm_input 16*1*384
lstm_input &#61; paddle.concat((x, context_vector), axis&#61;-1)
# LSTM requirement to previous hidden/state:
# (number_of_layers * direction, batch, hidden)
previous_hidden &#61; paddle.transpose(previous_hidden, [1, 0, 2])
previous_cell &#61; paddle.transpose(previous_cell, [1, 0, 2])
x, (hidden, cell) &#61; self.lstm(lstm_input, (previous_hidden, previous_cell))
# change the return to (batch, number_of_layers * direction, hidden)
hidden &#61; paddle.transpose(hidden, [1, 0, 2])
cell &#61; paddle.transpose(cell, [1, 0, 2])
output &#61; self.outlinear(hidden)
output &#61; paddle.squeeze(output)
return output, (hidden, cell)
接下来我们开始训练模型。
在每个epoch开始之前&#xff0c;我们对训练数据进行了随机打乱。
我们通过多次调用atten_decoder&#xff0c;在这里实现了解码时的recurrent循环。
teacher forcing策略: 在每次解码下一个词时&#xff0c;我们给定了训练数据当中的真实词作为了预测下一个词时的输入。相应的&#xff0c;你也可以尝试用模型预测的结果作为下一个词的输入。&#xff08;或者混合使用&#xff09;
encoder &#61; Encoder()
decoder &#61; Decoder()
opt &#61; paddle.optimizer.Adam(learning_rate&#61;0.001,
parameters&#61;encoder.parameters()&#43;decoder.parameters())
for epoch in range(epochs):
print("epoch:".format(epoch))
# shuffle training data
perm &#61; np.random.permutation(len(train_en_sents))
train_en_sents_shuffled &#61; train_en_sents[perm]
train_cn_sents_shuffled &#61; train_cn_sents[perm]
train_cn_label_sents_shuffled &#61; train_cn_label_sents[perm]
for iteration in range(train_en_sents_shuffled.shape[0] // batch_size):
x_data &#61; train_en_sents_shuffled[(batch_size*iteration):(batch_size*(iteration&#43;1))]
sent &#61; paddle.to_tensor(x_data)
en_repr &#61; encoder(sent)
x_cn_data &#61; train_cn_sents_shuffled[(batch_size*iteration):(batch_size*(iteration&#43;1))]
x_cn_label_data &#61; train_cn_label_sents_shuffled[(batch_size*iteration):(batch_size*(iteration&#43;1))]
# shape: (batch, num_layer(&#61;1 here) * num_of_direction(&#61;1 here), hidden_size)
hidden &#61; paddle.zeros([batch_size, 1, hidden_size])
cell &#61; paddle.zeros([batch_size, 1, hidden_size])
loss &#61; paddle.zeros([1])
# the decoder recurrent loop mentioned above
for i in range(MAX_LEN &#43; 2):
cn_word &#61; paddle.to_tensor(x_cn_data[:,i:i&#43;1])
cn_word_label &#61; paddle.to_tensor(x_cn_label_data[:,i])
logits, (hidden, cell) &#61; decoder(cn_word, hidden, cell, en_repr)
step_loss &#61; F.cross_entropy(logits, cn_word_label)
loss &#43;&#61; step_loss
loss &#61; loss / (MAX_LEN &#43; 2)
if(iteration % 200 &#61;&#61; 0):
print("iter , loss:".format(iteration, loss.numpy()))
loss.backward()
opt.step()
opt.clear_grad()
输出结果如下图6所示&#xff1a;
根据你所使用的计算设备的不同&#xff0c;上面的训练过程可能需要不等的时间。&#xff08;在一台Mac笔记本上&#xff0c;大约耗时15~20分钟&#xff09; 完成上面的模型训练之后&#xff0c;我们可以得到一个能够从英文翻译成中文的机器翻译模型。接下来我们通过一个greedy search来实现使用该模型完成实际的机器翻译。&#xff08;实际的任务中&#xff0c;你可能需要用beam search算法来提升效果&#xff09;
encoder.eval()
decoder.eval()
num_of_exampels_to_evaluate &#61; 10
indices &#61; np.random.choice(len(train_en_sents), num_of_exampels_to_evaluate, replace&#61;False)
x_data &#61; train_en_sents[indices]
sent &#61; paddle.to_tensor(x_data)
en_repr &#61; encoder(sent)
word &#61; np.array(
[[cn_vocab[&#39;
)
word &#61; paddle.to_tensor(word)
hidden &#61; paddle.zeros([num_of_exampels_to_evaluate, 1, hidden_size])
cell &#61; paddle.zeros([num_of_exampels_to_evaluate, 1, hidden_size])
decoded_sent &#61; []
for i in range(MAX_LEN &#43; 2):
logits, (hidden, cell) &#61; decoder(word, hidden, cell, en_repr)
word &#61; paddle.argmax(logits, axis&#61;1)
decoded_sent.append(word.numpy())
word &#61; paddle.unsqueeze(word, axis&#61;-1)
results &#61; np.stack(decoded_sent, axis&#61;1)
for i in range(num_of_exampels_to_evaluate):
en_input &#61; " ".join(filtered_pairs[indices[i]][0])
ground_truth_translate &#61; "".join(filtered_pairs[indices[i]][1])
model_translate &#61; ""
for k in results[i]:
w &#61; list(cn_vocab)[k]
if w !&#61; &#39;
model_translate &#43;&#61; w
print(en_input)
print("true: ".format(ground_truth_translate))
print("pred: ".format(model_translate))
输出结果如下图7所示&#xff1a;
本系列文章内容为根据清华社出版的《自然语言处理实践》所作的相关笔记和感悟&#xff0c;其中代码均为基于百度飞桨开发&#xff0c;若有任何侵权和不妥之处&#xff0c;请私信于我&#xff0c;定积极配合处理&#xff0c;看到必回&#xff01;&#xff01;&#xff01;
最后&#xff0c;引用本次活动的一句话&#xff0c;来作为文章的结语&#xff5e;(&#xffe3;▽&#xffe3;&#xff5e;)~&#xff1a;
【学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。】
ps&#xff1a;更多精彩内容还请进入本文专栏&#xff1a;人工智能&#xff0c;进行查看&#xff0c;欢迎大家支持与指教啊&#xff5e;(&#xffe3;▽&#xffe3;&#xff5e;)~